using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Timers;
using Sio.Mdm.Utils;
using System.Reflection;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
using System.Threading;

namespace Sio.Mdm.Server
{
    partial class Server : Administration.IServerAdministration
    {
        #region Fields
        private int IdleJobId;        
        #endregion

        #region Properties

        #region Private

        #region AdminServer
        private Administration.IServerAdministration AdminServer
        {
            get
            {
                return this as Administration.IServerAdministration;
            }

        }
        #endregion

        #region Logged
        private bool _logged;
        private bool Logged
        {
            get
            {
                return _logged;
            }
            set
            {
                if (_logged == value)
                {
                    return;
                }

                _logged = value;
            }
        }
        #endregion

        #region Commands
        private static Dictionary<string, Administration.IServerCommand> _commands;
        private Dictionary<string, Administration.IServerCommand> Commands
        {
            get
            {
                if (null == _commands)
                {
                    _commands = new Dictionary<string, Administration.IServerCommand>();
                    RegisterRemoteCommands();
                }

                return _commands;
            }
        }
	    #endregion

        #region AvailableCommands
        private string[] _serverCommands;
        string[] Administration.IServerAdministration.AvailableCommands
        {
            get
            {
                if (null == _serverCommands)
                {
                    _serverCommands = new string[Commands.Count];
                    _commands.Keys.CopyTo(_serverCommands, 0);
                }

                return _serverCommands;
            }
        }
        #endregion

        #endregion

        #endregion        

        #region Methods

        #region Public
        
        #region IServerAdministration Members

        #region Login, Logout
        bool Administration.IServerAdministration.Login(string userName, string password)
        {
            EnsureAdminEnabled();

            if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(password))
            {
                return false;
            }

            bool success = Logged = userName == Config.RemoteUser && CheckPassword(password);

            if (success)
            {
                IdleJobId = InternalScheduler.ScheduleJob(Schedule.ByMinute, DateTime.Now, "Admin.Idle");
                InternalScheduler.ExecuteJob += InternalScheduler_Idle;

                Logger.WriteInformation("User logged in '{0}'", userName);
            }
            else
            {
                Logger.WriteInformation("User login failed '{0}'", userName);
            }

            return success;
        }
        void Administration.IServerAdministration.Logout()
        {
            EnsureAdminEnabled();

            if (!Logged)
            {
                return;
            }
            
            Logger.WriteInformation("User logged out.");

            Logged = false;
            InternalScheduler.RemoveJob(IdleJobId);
            InternalScheduler.ExecuteJob -= InternalScheduler_Idle;
        }
        string Administration.IServerAdministration.GetCommandDescription(string commandName)
        {           
            if (!Commands.ContainsKey(commandName))
            {
                return "No such command!";
            }

            return Commands[commandName].Description;
        }
        #endregion

        #region Registration
        void Administration.IServerAdministration.RegisterCommand(Administration.IServerCommand command)
        {
            if (Commands.ContainsKey(command.Name.ToLower()))
            {
                return;
            }

            Commands.Add(command.Name.ToLower(), command);
        }
        void Administration.IServerAdministration.RegisterCommand(Type commandType)
        {
            if (!typeof(Administration.IServerCommand).IsAssignableFrom(commandType))
            {
                throw new ArgumentException("Command must implement 'IServerCommand'");
            }

            Administration.IServerCommand cmd =
                Activator.CreateInstance(commandType) as Administration.IServerCommand;
            AdminServer.RegisterCommand(cmd);
        }
        void Administration.IServerAdministration.RegisterCommand(string commandTypeName)
        {
            try
            {
                Type type = Type.GetType(commandTypeName);
                AdminServer.RegisterCommand(type);
            }
            catch (ReflectionTypeLoadException ex)
            {
                Logger.WriteError("Error loading server command from type name!");
                Logger.WriteException(ex);

                foreach (Exception loadException in ex.LoaderExceptions)
                {
                    Logger.WriteException(loadException);
                }
            }
        }
        #endregion

        #region Commands
        string Administration.IServerAdministration.ExecuteCommand(Administration.IServerCommand command)
        {
            string result = null;

            try
            {
                result = ExecuteCommandInternal(command.Name, command.Parameters);
            }
            catch (ArgumentException ex)
            {
                result = String.Format("Command argument exception ({0})", ex.ToString());
            }
            catch (Exception ex)
            { 
                result = String.Format("Command execution failed with '{0}'", ex.ToString());
            }

            return result;
        }
        string Administration.IServerAdministration.ExecuteCommand(string commandLine)
        {
            if (String.IsNullOrEmpty(commandLine))
            {
                return String.Empty;
            }

            string[] line = commandLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            string[] arguments = new string[line.Length - 1];

            if (line.Length > 1)
            {
                line.CopyTo(arguments, 1);
            }            

            return ExecuteCommandInternal(line[0].ToLower(), arguments);
        }
        #endregion

        #endregion

        #endregion

        #region Private

        #region ExecuteCommandInternal
        private string ExecuteCommandInternal(string commandName, params string[] args)
        {
            EnsureAdminEnabled();            

            if(Commands.ContainsKey(commandName))
            {
                throw new ArgumentException("No such command!");
            }

            Administration.IServerCommand cmd = Commands[commandName];

            if (cmd.AuthorizationRequired)
            {
                EnsureLoggedIn();
            }
            
            try
            {
                cmd.Server = this;
                cmd.Parameters = args;
                cmd.Execute();
            }
            catch (Exception ex)
            {
                throw new Exception("Command error", ex);
            }

            return cmd.Result;
        }
        #endregion

        #region CheckPassword
        internal bool CheckPassword(string password)
        {   
            return EncodePassword(password) == Config.RemotePassword;
        }
        #endregion

        #region EncodePassword
        internal static string EncodePassword(string password)
        {
            SHA256Managed m = new SHA256Managed();
            UTF8Encoding encoding = new UTF8Encoding();            
            return Convert.ToBase64String(m.ComputeHash(encoding.GetBytes(password)));
        }
        #endregion

        #region EnsureAdminEnabled
        /// <summary>
        /// Simply check wheater call is legal
        /// </summary>
        private void EnsureAdminEnabled()
        {
            if (!Config.IsAdminEnabled)
            {
                throw new NotImplementedException();
            }
        }
        #endregion

        #region EnsureLoggedIn
        /// <summary>
        /// Make sure session is valid
        /// </summary>
        private void EnsureLoggedIn()
        {
            if (!Logged)
            {
                throw new InvalidOperationException("Current session is not valid!");
            }
        }
        #endregion

        #region RestartIdleTimer
        private void RestartIdleTimer()
        {
            InternalScheduler.RescheduleJob(IdleJobId);
        }
        #endregion

        #endregion

        #region Internal

        #region RegisterRemoteCommands
        internal void RegisterRemoteCommands()
        {
            Logger.WriteInformation("Registering remote administration commands");
            
            AdminServer.RegisterCommand(typeof(ServerCommands.ChangePassword));
            AdminServer.RegisterCommand(typeof(ServerCommands.JobScheduler));
            AdminServer.RegisterCommand(typeof(ServerCommands.Ping));

            foreach (string service in Config.Commands)
            {
                try
                {
                    // If 'IServerCommand' is implemented this should work
                    Administration.IServerCommand instance =
                        GetService(service) as Administration.IServerCommand;

                    if (instance != null)
                    {
                        AdminServer.RegisterCommand(instance);
                    }
                }
                catch (Exception ex)
                {
                    Logger.WriteError("Error registering server command!");
                    Logger.WriteException(ex);
                }
            }

            // Check for additional commands
            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                RegisterCommand(asm);
            }

            // And observe aassembly loading for additional commands
            AppDomain.CurrentDomain.AssemblyLoad += 
                delegate(object sender, AssemblyLoadEventArgs args)
            {
                RegisterCommand(args.LoadedAssembly);
            };            

            Logger.WriteInformation("Remote administration commands registered");
        } 
        #endregion

        #region CreateAdministrationServer
        internal static void CreateAdministrationServer()
        {
             // TODO: Must be implemented
        }
        #endregion

        #region RegisterCommand
        private void RegisterCommand(Assembly asm)
        {
            Type[] types = asm.GetExportedTypes();

            foreach (Type type in types)
            {
                if (typeof(Administration.IServerCommand).IsAssignableFrom(type))
                {
                    AdminServer.RegisterCommand(type);
                }
            }
        }
        #endregion

        #endregion

        #endregion

        #region Events

        #region Private

        #region InternalScheduler_Idle
        private void InternalScheduler_Idle(object sender, ExecuteJobEventArgs e)
        {
            if (e.Job.ID == IdleJobId)
            {
                Logger.WriteInformation("User idle. Loggin out.");
                (this as Administration.IServerAdministration).Logout();
            }
        }
        #endregion  

        #endregion

        #endregion

        #region Classes
        // this classes are currently highly experimental and should not be used
        // without trrained hand ;)
        #region SinkProvider
        class SinkProvider : IServerFormatterSinkProvider, IServerChannelSinkProvider
        {
            IServerChannelSinkProvider _Next;

            public SinkProvider()
            {
            }

            #region IServerChannelSinkProvider Members

            public IServerChannelSink CreateSink(IChannelReceiver channel)
            {
                return null;
            }

            public void GetChannelData(IChannelDataStore channelData)
            {
                
            }

            public IServerChannelSinkProvider Next
            {
                get
                {
                    return _Next;
                }
                set
                {
                    _Next = value;
                }
            }
            #endregion

            class Sink : BinaryServerFormatterSink
            {
                public Sink
                    (
                    BinaryServerFormatterSink.Protocol protocol,
                    IServerChannelSink nextSink,
                    IChannelReceiver receiver
                    )
                    : base(protocol, nextSink, receiver)
                {
                }

                #region IServerChannelSink Members
                public new void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, object state, System.Runtime.Remoting.Messaging.IMessage msg, ITransportHeaders headers, System.IO.Stream stream)
                {
                    base.AsyncProcessResponse(sinkStack, state, msg, headers, stream);
                }
                public new System.IO.Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, object state, System.Runtime.Remoting.Messaging.IMessage msg, ITransportHeaders headers)
                {
                    return base.GetResponseStream(sinkStack, state, msg, headers);
                }
                public new IServerChannelSink NextChannelSink
                {
                    get
                    {
                        return base.NextChannelSink;
                    }
                }
                public new ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack, System.Runtime.Remoting.Messaging.IMessage requestMsg, ITransportHeaders requestHeaders, System.IO.Stream requestStream, out System.Runtime.Remoting.Messaging.IMessage responseMsg, out ITransportHeaders responseHeaders, out System.IO.Stream responseStream)
                {
                    return base.ProcessMessage(sinkStack, requestMsg, requestHeaders, requestStream, out responseMsg, out responseHeaders, out responseStream);
                }
                #endregion

                #region IChannelSinkBase Members

                public new System.Collections.IDictionary Properties
                {
                    get
                    {
                        return base.Properties;
                    }
                }

                #endregion
            }
        }
        #endregion 

        #endregion

        
    }
}
